/**
 * @author Nicolas Latore
 * @author Giuseppe Mendola
 */

package sa4

import javax.servlet.http._
import scala.xml._
import javax.servlet.ServletConfig
import sa4.form._
import sa4.session._
import sa4.template._
import sa4.routing._
import sa4.persistence._
import org.squeryl.KeyedEntity
import org.squeryl.SessionFactory
import org.squeryl.Session
import org.squeryl.adapters.H2Adapter
import scala.util.matching.Regex
import org.squeryl.adapters.H2Adapter
import java.sql.Timestamp
import java.sql.Date
import org.squeryl.PrimitiveTypeMode._
import org.squeryl.dsl.ast.LogicalBoolean
import org.squeryl.annotations.OptionType
import java.io.FileInputStream
import javax.imageio.ImageIO
import java.net.URL

// This is a very simple Scala servlet.
// If you rename this class, you must also change the file
// src/main/webapp/WEB-INF/web.xml to use the new name.
class UsiAdsServlet extends HttpServlet {
  type HandlerFunction = (HttpServletRequest, HttpServletResponse) => Unit
  val routing = new Routing()
  val template = new Template()
  val session = new SessionManagement()
  val persistence = new Persistence()

  /**
   * Function that initializes the servlet
   * @param config 		a SerletConfig that contain the configuration for the servlet
   */
  override def init(config: ServletConfig) {
    super.init(config)
    //Starting the database
    Class.forName("org.h2.Driver")

    SessionFactory.concreteFactory = Some(() =>
      Session.create(
        java.sql.DriverManager.getConnection("jdbc:h2:" + System.getProperty("user.dir") + "/test"),
        new H2Adapter))

    transaction {
      UsiAds.drop
    }

    transaction {
      UsiAds.create
    }

    transaction {
      UsiAds.users.insert(new User("Steve", "Steve Jobs", "blab@bla.com", "1234", "3452567"))
      UsiAds.users.insert(new User("Miky", "Mike Tyson", "iuhu@bla.com", "1234", "3452567"))
      UsiAds.adverts.insert(new Advertisment(new java.util.Date, "Sell", "New guitar never used, price 1000CHF", "Miky"))
    }

    //Adding some route
    //LOADING FILES
    routing.addRoute("/", startPage)
    routing.addRoute("/home", homePage)
    routing.addRoute("/script.js", getScript)
    routing.addRoute("/index.css", css)
    routing.addRoute("/PinboardBackground.png", imageBackground)
    routing.addRoute("/post.png", imageBackgroundPin)
    //LOADING TEMPLATES
    routing.addRoute("/showcase", showCase)
    routing.addRoute("/newUser", insertUser)
    routing.addRoute("/newAdvert", insertAdvert)
    routing.addRoute("/contact", getContact)
  }

  /**
   * Function that manages requests and response on the server
   * @param req 	an HttpServletRequest to the servlet
   * @param resp 	an HttpSErvletResponse from the servlet
   */
  override def service(req: HttpServletRequest, resp: HttpServletResponse) {
    //Session setup
    var currentContext = session.getCurrentContext
    currentContext = session.newContext(req, resp, currentContext)
    var cc = currentContext().session
    cc.setMaxInactiveInterval(30)

    //Routing dispatch
    if (routing.tableDispatch(req, resp))
      return
    resp.sendError(HttpServletResponse.SC_NOT_FOUND)

  }

  /**
   * Function that load the initial page of UsiAdsServlet.
   * @param req 	an HttpServletRequest to the servlet.
   * @param resp 	an HttpSErvletResponse from the servlet.
   */
  def startPage(req: HttpServletRequest, resp: HttpServletResponse) {
    session.updateSession
    val xml = template.loadPage("src/main/scala/sa4/index.html")
    val html = prepareForm(xml)
    val html2 = clientLog(req, resp)
    resp.getWriter.write(new PrettyPrinter(72, 2).formatNodes(html2))
    resp.setContentType("text/html")
  }

  /**
   * Function that load the initial page of UsiAdsServlet if the login was correct.
   * @param req 	an HttpServletRequest to the servlet.
   * @param resp 	an HttpSErvletResponse from the servlet.
   */
  def homePage(req: HttpServletRequest, resp: HttpServletResponse) {
    startPage(req, resp)
    //this will check if the session is valid or not, if not we will be redirected to the login.
    if (!req.isRequestedSessionIdValid) {
      var dest = resp.encodeRedirectURL("/")
      resp.sendRedirect(dest)
    }
  }

  /**
   * Function that load the css file for the html page of the application.
   * @param req 	an HttpServletRequest to the servlet.
   * @param resp 	an HttpSErvletResponse from the servlet.
   */
  def css(req: HttpServletRequest, resp: HttpServletResponse) {
    val css = template.loadFile("src/main/scala/sa4/index.css")
    resp.getWriter.write(css)
    resp.setContentType("text/css")
  }

  /**
   * Function that load the background image for the page.
   * @param req 	an HttpServletRequest to the servlet.
   * @param resp 	an HttpSErvletResponse from the servlet.
   */
  def imageBackground(req: HttpServletRequest, resp: HttpServletResponse) = {
    val img = template.loadImage("src/main/scala/sa4/PinboardBackground.png")
    val fileType = getServletContext().getMimeType("PinboardBackground.png")
    val out = resp.getOutputStream();
    resp.setContentType(fileType)
    ImageIO.write(img, "jpg", out);
  }

  /**
   * Function that load the the background image for the post in the showcase.
   * @param req 	an HttpServletRequest to the servlet.
   * @param resp	an HttpSErvletResponse from the servlet.
   */
  def imageBackgroundPin(req: HttpServletRequest, resp: HttpServletResponse) = {
    val img = template.loadImage("src/main/scala/sa4/post.png")
    val fileType = getServletContext().getMimeType("post.png")
    val out = resp.getOutputStream();
    resp.setContentType(fileType)
    ImageIO.write(img, "png", out);
  }

  /**
   * Function that load the script for the html page of the application.
   * @param req 	an HttpServletRequest to the servlet.
   * @param resp 	an HttpSErvletResponse from the servlet.
   */
  def getScript(req: HttpServletRequest, resp: HttpServletResponse) = {
    val js = template.loadFile("src/main/scala/sa4/script.js")
    resp.getWriter.write(js)
    resp.setContentType("text/javascript")
  }
  
  /**
   * Function that load template for the contact information.
   * @param req 	an HttpServletRequest to the servlet.
   * @param resp 	an HttpSErvletResponse from the servlet.
   */
  def getContact(req: HttpServletRequest, resp: HttpServletResponse) = {
    val xml = template.setUpTemplate("src/main/scala/sa4/templateContact.xml")
    val html = prepareForm(xml)
    resp.getWriter.write(new PrettyPrinter(72, 2).formatNodes(html))
    if (!req.isRequestedSessionIdValid) {
      var dest = resp.encodeRedirectURL("/")
      resp.sendRedirect(dest)
    }
  }

  /**
   * Function that load the template for the login.
   * @param req an HttpServletRequest to the servlet.
   * @param resp an HttpSErvletResponse from the servlet.
   */
  def clientLog(req: HttpServletRequest, resp: HttpServletResponse) = {
    val xml = template.setUpTemplate("src/main/scala/sa4/templateLoginShop.xml")
    val html = prepareForm(xml)
    html
  }

  /**
   * Function that load the template for the registration of a new user.
   * @param req 	an HttpServletRequest to the servlet.
   * @param resp 	an HttpSErvletResponse from the servlet.
   */
  def insertUser(req: HttpServletRequest, resp: HttpServletResponse) {
    session.updateSession
    val ns = template.setUpTemplate("src/main/scala/sa4/NewUser.xml")
    val html = prepareForm(ns)
    resp.getWriter.write(new PrettyPrinter(72, 2).formatNodes(html))
    if (!req.isRequestedSessionIdValid) {
      var dest = resp.encodeRedirectURL("/")
      resp.sendRedirect(dest)
    }
  }

  /**
   * Function that load the template for the creation of a new advert.
   * @param req 	an HttpServletRequest to the servlet.
   * @param resp 	an HttpSErvletResponse from the servlet.
   */
  def insertAdvert(req: HttpServletRequest, resp: HttpServletResponse) {
    session.updateSession
    val ns = template.setUpTemplate("src/main/scala/sa4/newAdvert.xml")
    val html = prepareForm(ns)
    resp.getWriter.write(new PrettyPrinter(72, 2).formatNodes(html))
    if (!req.isRequestedSessionIdValid) {
      var dest = resp.encodeRedirectURL("/")
      resp.sendRedirect(dest)
    }
  }

  /**
   * Function that load will retrive all the adverts in the database.
   * @param req 	an HttpServletRequest to the servlet.
   * @param resp 	an HttpSErvletResponse from the servlet.
   */
  def getAds(req: HttpServletRequest, resp: HttpServletResponse) {
    session.updateSession
    val ns = template.setUpTemplate("")
  }

  /**
   * Function that load the css file for the base page of the application.
   * @param name 	a String that represent the user name of a client.
   * @param pass	a String that represent the password of the client.
   * @return html 	a NodeSeq element that will be used in the handler function to write the response.
   */
  def login(userName: String, pass: String): Boolean = {
    transaction {

      var list = from(UsiAds.users)(c => select(c))

      var res = from(UsiAds.users)(c =>
        where(c.id === userName and c.pass === pass)
          select (c.id)).headOption

      if (res != None) {
        true
      } else {
        false
      }
    }
  }

  /**
   * Function that call the login function to see if the login is correct, and if it is the case it will redirect the user to the home, otherwise we have to retry the login.
   * @param req 	an HttpServletRequest to the servlet.
   * @param resp 	an HttpSErvletResponse from the servlet.
   */
  def loginHandler(req: HttpServletRequest, resp: HttpServletResponse) = {
    val user = req.getParameter("user")
    val pass = req.getParameter("password")
    val loginResult = login(user, pass)
    if (loginResult) {
      var dest = resp.encodeRedirectURL("/home")
      resp.sendRedirect(dest)
    } else {
      var dest = resp.encodeRedirectURL("/")
      resp.sendRedirect(dest)
    }
  }

  /**
   * Function that call the post advert function to add a new advert in the database, after this operation the user will be redirected to the showcase page.
   * @param req 	an HttpServletRequest to the servlet.
   * @param resp 	an HttpSErvletResponse from the servlet.
   */
  def postAdvertHandler(req: HttpServletRequest, resp: HttpServletResponse) {
    val header = req.getParameter("header")
    val description = req.getParameter("description")
    val user = req.getParameter("user")
    postAdvert(header, description, user)
    var dest = resp.encodeRedirectURL("/showcase")
    resp.sendRedirect(dest)
  }

  /**
   * Function that call the post data function to add a new user in the database.
   * @param req 	an HttpServletRequest to the servlet.
   * @param resp 	an HttpSErvletResponse from the servlet.
   */
  def postDataHandler(req: HttpServletRequest, resp: HttpServletResponse) {
    val username = req.getParameter("userName")
    val description = req.getParameter("name")
    val email = req.getParameter("email")
    val pass = req.getParameter("pass")
    val number = req.getParameter("number")
    postData(username, description, email, pass, number)
    val html = template.setUpTemplate("src/main/scala/sa4/templateAddedUser.xml")
    resp.getWriter.write(new PrettyPrinter(72, 2).formatNodes(prepareForm(html)))
  }

  /**
   * Function that generate and load the template to list all the advert.
   * @param req 	an HttpServletRequest to the servlet.
   * @param resp 	an HttpSErvletResponse from the servlet.
   */
  def showCase(req: HttpServletRequest, resp: HttpServletResponse) {
    session.updateSession
    var a = """<surround with="src/main/scala/sa4/index.html" at="reg">"""
    for (i <- listAds) {
      a = a + i
    }
    a = a + "</surround>"

    val xml = template.processTemplate(XML.loadString(a))
    val html = prepareForm(xml)
    resp.getWriter.write(new PrettyPrinter(72, 2).formatNodes(html))
    if (!req.isRequestedSessionIdValid) {
      var dest = resp.encodeRedirectURL("/")
      resp.sendRedirect(dest)
    }
  }

  /**
   * Function that parse a template and install in the route table a new route with his handler function.
   * @param template a NodeSeq that represent the template of the form we want to process.
   * @return 	a NodeSeq that contain the template changed.
   */
  def prepareForm(template: NodeSeq): NodeSeq = {
    import scala.xml.transform._
    import scala.xml.Null // hide scala.Null
    object MakeForm extends RewriteRule {
      override def transform(n: Node): Seq[Node] = n match {
        case form: Elem if form.label == "form" =>
          val Some(handler) = form.attribute("handler")
          var Some(method) = form.attribute("method")

          // Make the handler and install in routeTable
          val (uri, f: HandlerFunction) = makeHandler(handler.text)

          routing.addRoute(uri, f)

          // Add an action to the form.
          form % Attribute(None, "action", Text(uri), Null)
        case n => n
      }
    }
    template.map(ns => new RuleTransformer(MakeForm)(ns))
  }

  /**
   * Function that parse a header method and creates a new route and install it in the route table with its corresponding handler function.
   * @param s 	a String that represent the header of the method.
   */
  def makeHandler(s: String): (String, HandlerFunction) = {
    // Parse the handler string.
    val q = "^(\\w+)\\((.*)\\)$".r

    val (methodName, argNames) = s match {
      case q(methodName, argNames) => (methodName, argNames.split(",").map(_.trim))
      case _ => throw new RuntimeException("bad handler: " + s)
    }

    val h = (req: HttpServletRequest, resp: HttpServletResponse) => {
      methodName match {
        case "login" =>
          loginHandler(req, resp)
        case "postAdvert" =>
          postAdvertHandler(req, resp)
        case "postData" =>
          postDataHandler(req, resp)
      }
    }
    (handlerURI(methodName), h)
  }

  /**
   * Function that constructs a new route with the method name.
   * @param methodName 	a String that contain the method that will be used as path.
   */
  def handlerURI(methodName: String) = {
    "/" + methodName
  }

  /*
   * *************persistence*******************
   */

  /**
   * Function that post the data of a new post in the database.
   * @param header 		a String with the header of the advert.
   * @param description a String that contain the description of the advert.
   * @param user 		a String that represent the name of the user that posted the advert.
   * @return			a NodeSeq that inform the user that the transaction end with success.
   */
  def postAdvert(header: String, description: String, user: String) = {
    persistence.newAdvert(header, description, user)
  }

  /**
   * Function that post the data of a new user in the dabapase.
   * @param userName	a String that contain the user name of the user.
   * @param name		a String that contain the real name of the user.
   * @param email		a String that contain the email of the user.
   * @param pass		a String that contain the password of the user.
   * @param number		a String that contain the number of the user.
   * @return			a NodeSeq that inform the user that the transaction end with success.
   */
  def postData(userName: String, name: String, email: String, pass: String, number: String) = {
    persistence.newUser(userName, name, email, pass, number)
  }

  /**
   * Function that list the adverts in the database.
   * @return		a NodeSeq that contain all the adverts.
   */
  def listAds = {
    transaction {
      for (a <- UsiAds.adverts)
        yield <div class="post">
                <div class="content">
                  <p><span>UserName: { a.user }</span></p>
                  <p><span>Type of Add: { a.header }</span></p>
                  <p><span>Decription: { a.description }</span></p>
                  <p><span>Date: { a.date }</span></p>
                  <p><a id="contactLink" href={
                    var res = from(UsiAds.users)(c =>
                      where(c.id === a.user)
                        select (c.email)).headOption
                    "mailto:" + res.mkString
                  }></a><input id="contactButton" type="button" value={ "Contact " + a.user.mkString }/></p>
                </div>
              </div>
    }
  }
}